package objReader;

import java.util.ArrayList;
import java.util.Random;

import math_generic.Vector3;
import org.lwjgl.opengl.GL11;
import org.newdawn.slick.opengl.Texture;

public class objModel 
{
	/* State */
	Vector3 position;
	Vector3 rotation;
	Vector3 velocity; // Global 
	Vector3 acceleration;
	float heading;
	float mass;
	
	/* Information */
	String name;
	boolean clone;	/* Some objects are clone of others. */

	/* Random generator */
	Random rand;
	
	/* Geometry */
	public ArrayList<Vertex3> vertices;
	public ArrayList<Vertex3> normals;
	public ArrayList<Vertex2> textures;	
	public ArrayList<Face> faceIndices;
	//public ArrayList<Material> materials; 
	public int materialIndex;
	public Texture texture;
	public boolean usesTexture;
	
	/* Re-usable objModel object */
	objModel tmp;
	
	public objModel(String modelName)
	{
		this.name = modelName;
		this.clone = false;
		
		/* init state */
		position = new Vector3(0, 0, 0);
		rotation = new Vector3(0, 0, 0);
		velocity = new Vector3(0, 0, 0);
		acceleration = new Vector3(0, 0, 0);
		
		heading = 0;
		mass = 0;
		
		/* init geometry */
		vertices = new ArrayList<Vertex3>();
		normals = new ArrayList<Vertex3>();
		textures = new ArrayList<Vertex2>();
		faceIndices = new ArrayList<Face>();
		//materials = new ArrayList<Material>();
		
		rand = new Random();
	}
	
	public objModel(String modelName, boolean randomPosition)
	{
		this(modelName);
		
		if(randomPosition)
			setRandomPosition();
	}
	
	public void setRandomPosition()
	{
		setPosition(100*rand.nextFloat()-50, 
					/*100*rand.nextFloat()-50*/ 0, 
					50*rand.nextFloat());
	}
	
	public void move(float xincr, float yincr, float zincr)
	{
		position.x += xincr;
		position.y += yincr;
		position.z += zincr;
	}
	
	public void setPosition(float newx, float newy, float newz)
	{
		position.x = newx;
		position.y = newy;
		position.z = newz;
	}
	
	public void setVelocity(float newvelx, float newvely, float newvelz)
	{
		velocity.x = newvelx;
		velocity.y = newvely;
		velocity.z = newvelz;
	}
	
	public void setAcceleration()
	{
		
	}
	
	public boolean isClone()
	{
		return clone;
	}
	
	public void setCloneStatus(boolean val)
	{
		clone = val;
	}
	
	public void setHeading(float newHeading)
	{
		heading = newHeading;
	}
	
	public void rotate(float xincr, float yincr, float zincr)
	{
		rotation.x += xincr;
		rotation.y += yincr;		
		rotation.z += zincr;		
	}
	
	public void setTexture(Texture texture)
	{
		this.texture = texture;
		this.usesTexture = true;
	}
	
	public Texture getTexture()
	{
		return texture;
	}
	
	/* An object is made by a series of faces (each having 3 vertices). A face 
	 * can either be textured by a color, or by an actual texture file. It is 
	 * necessary to constraint each texture to an object, instead of a texture
	 * to each face. This is due to the fact that more textures would have to
	 * be loaded from memory, and each call to glBindTexture is CPU expensive.
	 * A possible solution is to have a big texture file from which an object 
	 * can map to its different faces. This way, only 1 call to glBindTexture
	 * would be needed when rendering the object. */
	public void renderModel(boolean renderFaceEdges)
	{
		/* Render model */
		int count = 0;
		
		/* Save matrix state */
		GL11.glPushMatrix();

		/* Translate to the object's position */
		GL11.glTranslatef(position.x, 
						  position.y, 
						  position.z);
		
		GL11.glRotatef(rotation.x, 1.0f, 0, 0);
		GL11.glRotatef(rotation.y, 0, 1.0f, 0);
		GL11.glRotatef(rotation.z, 0, 0, 1.0f);
		
		/* Draw edges */
		/*GL11.glDisable(GL11.GL_TEXTURE_2D);
		GL11.glEnable(GL11.GL_COLOR_MATERIAL);
		
		if(renderFaceEdges)
		{
			GL11.glColor3f(0,0,0); // Black
			GL11.glBegin(GL11.GL_LINES);
			for(count = 0; count < getFaceCount(); count++)
			{
				// face(i) gives the 3 vertices of each triangle
				drawLine(vertices.get((int)faceIndices.get(count).v_index.x - 1).x,
						 vertices.get((int)faceIndices.get(count).v_index.x - 1).y,
						 vertices.get((int)faceIndices.get(count).v_index.x - 1).z,
						 vertices.get((int)faceIndices.get(count).v_index.y - 1).x,
						 vertices.get((int)faceIndices.get(count).v_index.y - 1).y,
						 vertices.get((int)faceIndices.get(count).v_index.y - 1).z);
				
				drawLine(vertices.get((int)faceIndices.get(count).v_index.y - 1).x,
						 vertices.get((int)faceIndices.get(count).v_index.y - 1).y,
						 vertices.get((int)faceIndices.get(count).v_index.y - 1).z,
						 vertices.get((int)faceIndices.get(count).v_index.z - 1).x,
						 vertices.get((int)faceIndices.get(count).v_index.z - 1).y,
						 vertices.get((int)faceIndices.get(count).v_index.z - 1).z);
				
				drawLine(vertices.get((int)faceIndices.get(count).v_index.z - 1).x,
						 vertices.get((int)faceIndices.get(count).v_index.z - 1).y,
						 vertices.get((int)faceIndices.get(count).v_index.z - 1).z,
						 vertices.get((int)faceIndices.get(count).v_index.x - 1).x,
						 vertices.get((int)faceIndices.get(count).v_index.x - 1).y,
						 vertices.get((int)faceIndices.get(count).v_index.x - 1).z);
			}
			GL11.glEnd();
		}*/
		
		/* Draw faces */
		if(this.usesTexture)
		{
			/* Enable textures and bind the right one to its face */
			GL11.glDisable(GL11.GL_COLOR_MATERIAL);
			GL11.glEnable(GL11.GL_TEXTURE_2D);
			texture.bind();
		}
		else
		{
			GL11.glDisable(GL11.GL_TEXTURE_2D);
			GL11.glEnable(GL11.GL_COLOR_MATERIAL);
		}
		
		GL11.glBegin(GL11.GL_TRIANGLES);
		for(int face = 0; face < getFaceCount(); face++ ) /* For all faces (TRIANGLES) */
		{
			if(this.usesTexture)
			{
				GL11.glTexCoord2f(ModelReader.texCoord.get((int)faceIndices.get(face).vt_index.x - 1).x, 
								  ModelReader.texCoord.get((int)faceIndices.get(face).vt_index.x - 1).y);
			}
			else
			{
				GL11.glColor3f(ModelReader.materials.get(faceIndices.get(face).materialIndex).R, 
							   ModelReader.materials.get(faceIndices.get(face).materialIndex).G, 
							   ModelReader.materials.get(faceIndices.get(face).materialIndex).B);  
			}
			
			GL11.glNormal3f(ModelReader.normals.get((int) faceIndices.get(face).vn_index.x - 1).x,
						    ModelReader.normals.get((int) faceIndices.get(face).vn_index.x - 1).y,
						    ModelReader.normals.get((int) faceIndices.get(face).vn_index.x - 1).z);
			GL11.glVertex3f(ModelReader.vertices.get((int)(faceIndices.get(face).v_index.x - 1)).x,
							ModelReader.vertices.get((int)(faceIndices.get(face).v_index.x - 1)).y,
							ModelReader.vertices.get((int)(faceIndices.get(face).v_index.x - 1)).z);
			
			if(this.usesTexture)
			{
				GL11.glTexCoord2f(ModelReader.texCoord.get((int)faceIndices.get(face).vt_index.y - 1).x, 
								  ModelReader.texCoord.get((int)faceIndices.get(face).vt_index.y - 1).y);
			}
			
			GL11.glNormal3f(ModelReader.normals.get((int) faceIndices.get(face).vn_index.y - 1).x,
							ModelReader.normals.get((int) faceIndices.get(face).vn_index.y - 1).y,
							ModelReader.normals.get((int) faceIndices.get(face).vn_index.y - 1).z);
			GL11.glVertex3f(ModelReader.vertices.get((int)(faceIndices.get(face).v_index.y - 1)).x,
							ModelReader.vertices.get((int)(faceIndices.get(face).v_index.y - 1)).y,
							ModelReader.vertices.get((int)(faceIndices.get(face).v_index.y - 1)).z);
			
			if(this.usesTexture)
			{
				GL11.glTexCoord2f(ModelReader.texCoord.get((int)faceIndices.get(face).vt_index.z - 1).x, 
								  ModelReader.texCoord.get((int)faceIndices.get(face).vt_index.z - 1).y);
			}
			
			GL11.glNormal3f(ModelReader.normals.get((int) faceIndices.get(face).vn_index.z - 1).x,
							ModelReader.normals.get((int) faceIndices.get(face).vn_index.z - 1).y,
							ModelReader.normals.get((int) faceIndices.get(face).vn_index.z - 1).z);
			GL11.glVertex3f(ModelReader.vertices.get((int)(faceIndices.get(face).v_index.z - 1)).x,
							ModelReader.vertices.get((int)(faceIndices.get(face).v_index.z - 1)).y,
							ModelReader.vertices.get((int)(faceIndices.get(face).v_index.z - 1)).z);

		}
		GL11.glEnd();
		
		/* Re-establish matrix state */
		GL11.glPopMatrix();
	}
	
	/* Function should be called within a glBegin(GL_LINES) < > glEnd() block. Otherwise, may not work */
	public void drawLine(float x1, float y1, float z1, float x2, float y2, float z2)
	{
		GL11.glVertex3f(x1, y1, z1);
		GL11.glVertex3f(x2, y2, z2);
	}

	/* 0 >= k >= inf */
	public void scaleObj(float kx, float ky, float kz)
	{
		ArrayList<Vertex3> newVertices = new ArrayList<Vertex3>();
		
		if(kx >= 0 && ky >= 0 && kz >= 0)
		{
			for(Vertex3 v : vertices)
				newVertices.add(v.scale(kx, ky, kz));
			
			vertices = newVertices;
			
			/* Make null so the GC automatically frees memory */
			newVertices = null;
		}
		else
		{
			System.err.println("Please select positive value for k");
		}
	}
	
	/* Only supports TRIANGLES for now */
	public int getFaceCount()
	{
		return faceIndices.size();
	}
	
	/* Under construction */
	public int getVerticesPerFace()
	{
		return 3; //getFaceVertices().size() - 1;
	}
	
	/* Under construction */
	public boolean hasTriangularFaces()
	{
		return true;
	}
	
	/* Under construction */
	public boolean hasQuadFaces()
	{
		return false;
	}
	
	public ArrayList<Vertex3> getFaceVertices()
	{
		return vertices;
	}
	
	public ArrayList<Vertex2> getTextureVertices()
	{
		return textures;
	}
	
	public ArrayList<Vertex3> getFaceNormals()
	{
		return normals;
	}
	
	public Vector3 getPosition()
	{
		return this.position;
	}
	
	/* Produces multiple clones of a model without having
	 * to read the model obj file again */
	public void cloneModel(objModel other)
	{
		/* Clone the geometry */
		other.vertices = this.vertices;
		other.textures = this.textures;
		other.normals = this.normals;
		other.faceIndices = this.faceIndices;
		//other.materials = this.materials;
		
		if(this.usesTexture)
			other.setTexture(this.getTexture());
		
		other.mass = this.mass;
		other.clone = true;		
	}
	
	public objModel cloneModel()
	{
		tmp = new objModel(this.name);
		cloneModel(tmp);
		
		return tmp;
	}
	
	public objModel cloneModel(boolean randomPosition)
	{
		tmp = cloneModel();
		
		if(randomPosition)
			tmp.setRandomPosition();
		
		return tmp;
	}
}
